import type { OutlookClient } from "@/utils/outlook/client";
import type {
  MessageRule,
  OutlookCategory,
} from "@microsoft/microsoft-graph-types";
import { createScopedLogger } from "@/utils/logger";
import { isAlreadyExistsError } from "./errors";
import { withOutlookRetry } from "@/utils/outlook/retry";
import { getLabelById } from "@/utils/outlook/label";

const logger = createScopedLogger("outlook/filter");

// Microsoft Graph API doesn't have a direct equivalent to Gmail filters
// Instead, we can work with mail rules which are more complex but provide similar functionality
// Note: Mail rules in Outlook are more limited and require different permissions

export async function createFilter(options: {
  client: OutlookClient;
  from: string;
  addLabelIds?: string[];
  removeLabelIds?: string[];
}) {
  const { client, from, addLabelIds, removeLabelIds } = options;

  try {
    const actions = await buildFilterActions({
      client,
      addLabelIds,
      removeLabelIds,
      context: { from },
    });

    const rule: MessageRule = {
      displayName: `Filter for ${from}`,
      sequence: 1,
      isEnabled: true,
      conditions: {
        senderContains: [from],
      },
      actions,
    };

    const response: MessageRule = await withOutlookRetry(() =>
      client.getClient().api("/me/mailFolders/inbox/messageRules").post(rule),
    );

    return { status: 201, data: response };
  } catch (error) {
    if (isAlreadyExistsError(error)) {
      logger.warn("Filter already exists", { from });
      return { status: 200 };
    }
    throw error;
  }
}

export async function createAutoArchiveFilter({
  client,
  from,
  labelName,
}: {
  client: OutlookClient;
  from: string;
  labelName?: string;
}) {
  try {
    // For Outlook, we'll create a rule that moves messages to archive
    const rule: MessageRule = {
      displayName: `Auto-archive filter for ${from}`,
      sequence: 1,
      isEnabled: true,
      conditions: {
        senderContains: [from],
      },
      actions: {
        moveToFolder: "archive",
        markAsRead: true,
        ...(labelName && { assignCategories: [labelName] }),
      },
    };

    const response: MessageRule = await withOutlookRetry(() =>
      client.getClient().api("/me/mailFolders/inbox/messageRules").post(rule),
    );

    return { status: 201, data: response };
  } catch (error) {
    if (isAlreadyExistsError(error)) {
      logger.warn("Auto-archive filter already exists", { from });
      return { status: 200 };
    }
    throw error;
  }
}

export async function deleteFilter(options: {
  client: OutlookClient;
  id: string;
}) {
  const { client, id } = options;

  try {
    await withOutlookRetry(() =>
      client
        .getClient()
        .api(`/me/mailFolders/inbox/messageRules/${id}`)
        .delete(),
    );

    return { status: 204 };
  } catch (error) {
    logger.error("Error deleting Outlook filter", { id, error });
    throw error;
  }
}

export async function getFiltersList(options: { client: OutlookClient }) {
  try {
    const response: { value: MessageRule[] } = await options.client
      .getClient()
      .api("/me/mailFolders/inbox/messageRules")
      .get();

    return response;
  } catch (error) {
    logger.error("Error getting Outlook filters list", {
      error,
      errorMessage: error instanceof Error ? error.message : "Unknown error",
      errorStack: error instanceof Error ? error.stack : undefined,
    });
    throw error;
  }
}

// Additional helper functions for Outlook-specific operations

export async function createCategoryFilter({
  client,
  from,
  categoryName,
}: {
  client: OutlookClient;
  from: string;
  categoryName: string;
}) {
  try {
    // First, ensure the category exists
    const categories: { value: OutlookCategory[] } = await client
      .getClient()
      .api("/me/outlook/masterCategories")
      .get();

    let category = categories.value.find(
      (cat) => cat.displayName === categoryName,
    );

    if (!category) {
      // Create the category if it doesn't exist
      category = await withOutlookRetry(() =>
        client.getClient().api("/me/outlook/masterCategories").post({
          displayName: categoryName,
          color: "preset0", // Default color
        }),
      );
    }

    // Note: Microsoft Graph API doesn't support applying categories directly in mail rules
    // This function creates the category but the actual application would need to be done
    // when processing individual messages, similar to how it's done in the label functions

    logger.info("Category created for filter", {
      from,
      categoryName,
      categoryId: category?.id,
    });

    return {
      status: 200,
      category,
      message:
        "Category created. Note: Categories must be applied to individual messages.",
    };
  } catch (error) {
    if (isAlreadyExistsError(error)) {
      logger.warn("Category filter already exists", { from, categoryName });
      return { status: 200 };
    }
    throw error;
  }
}

export async function updateFilter({
  client,
  id,
  from,
  addLabelIds,
  removeLabelIds,
}: {
  client: OutlookClient;
  id: string;
  from: string;
  addLabelIds?: string[];
  removeLabelIds?: string[];
}) {
  try {
    const actions = await buildFilterActions({
      client,
      addLabelIds,
      removeLabelIds,
      context: { id, from },
    });

    const rule: MessageRule = {
      displayName: `Filter for ${from}`,
      sequence: 1,
      isEnabled: true,
      conditions: {
        senderContains: [from],
      },
      actions,
    };

    const response: MessageRule = await withOutlookRetry(() =>
      client
        .getClient()
        .api(`/me/mailFolders/inbox/messageRules/${id}`)
        .patch(rule),
    );

    return response;
  } catch (error) {
    logger.error("Error updating Outlook filter", { id, error });
    throw error;
  }
}

// Helper functions

/**
 * Resolves label IDs to category names for Outlook rules.
 * Outlook rules use category names, not IDs.
 */
async function resolveCategoryNames(
  client: OutlookClient,
  labelIds: string[],
): Promise<string[]> {
  const categoryNames: string[] = [];

  for (const labelId of labelIds) {
    try {
      const category = await getLabelById({ client, id: labelId });
      if (category?.displayName) {
        categoryNames.push(category.displayName);
      } else {
        logger.error("Category not found by ID", { labelId });
      }
    } catch (error) {
      logger.error("Failed to resolve category ID", {
        labelId,
        error,
      });
    }
  }

  return categoryNames;
}

/**
 * Builds the actions object for Outlook message rules.
 * Microsoft API requires at least one action.
 */
async function buildFilterActions(options: {
  client: OutlookClient;
  addLabelIds?: string[];
  removeLabelIds?: string[];
  context?: Record<string, unknown>;
}): Promise<{
  moveToFolder?: string;
  markAsRead?: boolean;
  assignCategories?: string[];
}> {
  const { client, addLabelIds, removeLabelIds, context = {} } = options;
  const actions: {
    moveToFolder?: string;
    markAsRead?: boolean;
    assignCategories?: string[];
  } = {};

  // Handle label additions (categories in Outlook)
  if (addLabelIds && addLabelIds.length > 0) {
    const categoryNames = await resolveCategoryNames(client, addLabelIds);
    if (categoryNames.length > 0) {
      actions.assignCategories = categoryNames;
    }
  }

  // Handle label removals
  if (removeLabelIds?.includes("INBOX")) {
    actions.moveToFolder = "archive";
  }

  if (removeLabelIds?.includes("UNREAD")) {
    actions.markAsRead = true;
  }

  // If no actions were specified, default to marking as read
  // (Microsoft API requires at least one action)
  if (Object.keys(actions).length === 0) {
    logger.warn("No actions specified for filter, defaulting to markAsRead", {
      addLabelIds,
      removeLabelIds,
      ...context,
    });
    actions.markAsRead = true;
  }

  return actions;
}
